#include <memory>
#include "cppThreadPool.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/rotating_file_sink.h"

template<typename type>
poolQueue<type>::poolQueue() {
    spdlog::info("启用线程池！初始化线程池任务队列！");
}


template<typename type>
poolQueue<type>::~poolQueue() {
    spdlog::info("线程池任务队列析构！");
}

template<typename type>
void poolQueue<type>::add(type& task) {
    // 考虑并发问题 添加任务需要加锁
    std::unique_lock<std::mutex> lock(this->qMutex);
    this->taskQueue.emplace(task);
}

template<typename type>
bool poolQueue<type>::empty() {
    std::unique_lock<std::mutex> lock(this->qMutex);
    return this->taskQueue.empty();
}

template<typename type>
int poolQueue<type>::size() {
    std::unique_lock<std::mutex> lock(this->qMutex);
    return this->taskQueue.size();
}

template<typename type>
bool poolQueue<type>::getTask(type& task) {
    std::unique_lock<std::mutex> lock(this->qMutex);

    if (!this->taskQueue.empty()) {
        //任务队列为空直接返回
        return false;
    } 

    // 右值引用获取任务
    task = std::move(this->taskQueue.front());
    this->taskQueue.pop();

    return true;
}

// 工作线程部分
wordThread::wordThread(threadPool* p, int Id):workId(Id), pool(p) {
    spdlog::info("创建线程, workId 是 {%d}", this->workId);
}

wordThread::~wordThread() {
    spdlog::info("工作线程析构, workId 是 {%d}", this->workId);
}

void wordThread::operator()() {
    // 这里重载()是必须的,std::thread需要一个可调用对象
    // std::thread的第一个参数不止是函数，还可以是一个可调用对象，即支持operator()(Args...)操作
    
    std::function<void()> func; // 基础函数类

    bool isGetTask(false);
    
    while(this->pool->getPoolIsWork()) {
        // 检查线程是否工作， 如果关闭线程池，调度过程终止
        isGetTask = this->pool->getTaskFromQueue(func);

        if (isGetTask) {
            func();
        }
    }
}

bool threadPool::getPoolIsWork() {
    return this->poolIsWork;
}

bool threadPool::getTaskFromQueue(std::function<void()>& func) {
    
    std::unique_lock<std::mutex> lock(this->poolMutex);
    
    // 如果任务队列为空 阻塞线程  wait表示线程阻塞 等待被唤醒
    if (this->taskQueue.empty()) {
        // std::condition_variable conditionLock 注意该成员变量
        // 条件变量和锁机制搭配使用 执行到wait()函数的时候 自动的释放锁 线程阻塞
        this->conditionLock.wait(lock);
    }


    return this->taskQueue.getTask(func);
}

// 定义线程池其他函数

threadPool::threadPool(int threadNum):poolIsWork(true), threadVec(std::vector<std::thread>(threadNum)) {
    for (int threadId(0); threadId < threadNum; threadId++) {
        threadVec.at(threadId) = std::thread(wordThread(this, threadId));
    }
    spdlog::info("成功创建线程池！");
}

void threadPool::shutDown() {
    this->poolIsWork = false;
    this->conditionLock.notify_all(); // 唤醒所有工作线程, 执行关闭操作
    for (size_t idx(0); idx < this->threadVec.size(); idx++) {
        if (this->threadVec.at(idx).joinable()) {
            threadVec.at(idx).join(); // 如果线程没有执行结束，join等待
        }
    }

}


threadPool::~threadPool() {
    if (this->poolIsWork) {
        this->shutDown();
    }
    spdlog::info("成功析构线程池！");
}

// 线程池添加任务函数 支持任意类型任务
/*  
    函数说明：
    future部分
    {
        future是一个用来获取异步任务的结果，其存在的意义就是为了解决 std::thread 无法返回值的问题
        future可以想象成一个对未来的预言，定义它的时候，相当于某个预言家预言在未来的某一天会出现这个事件。

        预言只有一个，但预言家们倒是有很多个（即获得future的方式）:
            1.std::async 函数会返回一个std::future
            2.std::promise::get_future 调用成员函数，获取 std::future
            3.std::packaged_task::get_future 调用成员函数，获取 std::future
    }
    std::function 和 std::bind
    {
        参考链接：https://paul.pub/cpp-lambda-function-bind/
        而std::bind可以将调用函数时的部分参数先制定好，留下一部分在真正调用时确定。
        std::bind函数可以配合function函数使用，例如某些特殊情况，function对象只接受一个int类型，但是这时候有需求是
        直对范围在20到40之间的数据做处理，整体该函数结构没有必要，可以使用bind
        std::bind(isBetween, placeholders::_1, 20, 40); //placeholders占位符
    }

    std::forward
    {
        在C++中,无论以什么形式传递，函数参数都是左值，如果函数中调用另外的函数，假设是右值参数，那么就会出现错误
    }
*/
template<typename F, typename... Args>
auto threadPool::addTask(F&& f, Args&&... args)->std::future<decltype(std::forward<F>(f)(std::forward<Args>(args)...))> {
    // 创建函数对象，通过bind创建固定参数函数实体，可以直接调用
    std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
    
    // 来绑定回调函数
    /*
        std::packaged_task<decltype(f(args...))()> task(func);
        问题：为什么还需要封装一层智能指针
        答：function被包装的对象需要是一个可复制的可调用对象。
        而package_task是禁止复制操作的，仅支持移动操作，所以需要一个再次包装。
    */
    
    auto taskPtr = std::make_shared<std::packaged_task<decltype(std::forward<F>(f)(std::forward<Args>(args)...))()>>(func);
    
    std::function<void()> queueTask = [taskPtr] {
        (*taskPtr)();
    };

    this->taskQueue.add(queueTask);
    // 唤醒一个阻塞的线程 
    this->conditionLock.notify_one();

    //返回异步结果future
    return taskPtr->get_future();
}
